
/* LICENSE GNU GPL 
 * 
 * FILE: implementation of the simple ini file parser.
 *       Feel free to use this in your commercial or non-commercial software.
 * 
 * 
 * see ./test/config_file_test.cpp for a test example 
 * 
 * Written By: sandundhammikaperera@gmail.com
 * 
 */

#include "config_file.h"
#include "common.h"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


static FILE* fd=0; /* file descriptor for the open file */

static int is_parsed = FALSE ;
typedef struct tag_key_value 
{
  char* key ;
  char* value ;
  tag_key_value * next;
} key_value;

typedef struct tag_session{
  char* session_name ;
  key_value *head_key_value;
  struct tag_session* next; 
} session;

session * head;
session * current_session;

static session* get_next_session(session *current_session)
{
  if ( current_session == NULL)
    std::cerr << "ConfigFile: current session is NULL.\n";
  
  return current_session->next;
}

int config_file_session_into(const char * session_name)
{
  /* check for preconditions */
  if(session_name == NULL)
  {
   std::cerr << "ConfigFile:config_file_session_into could not enter \
                \n          into NULL session.\n";
   exit(0);
  }
  if( strcmp( session_name ,"")==0){
   std::cerr <<"ConfigFile:config_file_session_into could not enter into\
               \n\"\"session.\n";
   exit(0);
  }

  if(current_session == NULL)
  {
    //std::cerr << "ConfigFile:You need to call config_file_parse() before config_file_session_into(). \n";
    return FALSE;
  }
  
  session* previous_session = current_session;
  current_session = head;
  while(TRUE)
  {
    if( strcmp( session_name , current_session->session_name ) != 0)
    {
      current_session = current_session->next;
      if(current_session == NULL){
	/* we didn't find the session that we looking for 
	 * This is the termination condition.
	 */
	current_session =previous_session;
	return FALSE;
      }
    }else{
      ::current_session = current_session;
     return TRUE;  
    }
  }
}

const char* config_file_get_current_session()
{
  if(current_session == NULL)
    return NULL;
  else
    return current_session->session_name;
}

const char* config_file_get_key_value(const char* keyword)
{
  if( current_session == NULL)
  {
    std::cerr << "ConfigFile: Please step into a session before calling \
                 \n           config_file_get_key_value().\n";
    exit(0);
  }
  
  key_value* current_key_value = current_session->head_key_value;
  while(TRUE)
  {
   if( current_key_value == NULL)
   {
     std::cerr << "ConfigFile: Required Keyword '" << keyword <<"' could not be\nfound\n";
     return NULL;
   }
   if ( strcmp( current_key_value->key,keyword) != 0)
   {
     current_key_value = current_key_value->next;
     continue;
   }else{
     char * ret_ = strdup(current_key_value->value);
     return ret_;     
   }
  }  
}

/* .some utility macros. */
#ifdef IS_NUM
#undef IS_NUM
#endif


#define IS_WHITE_SPACE(X)     (strchr(" \t",X)?TRUE:FALSE) 
#define IS_UNDERSCORE(X)         ( ((X) == '_' )?TRUE:FALSE )
#define IS_NEW_LINE(X)        (strchr("\n\r",X)?TRUE:FALSE)
#define IS_UPPER_ALPHA(X)     ((X <= 'Z' && X >= 'A')?TRUE:FALSE)
#define IS_LOWER_ALPHA(X)     ((X <='z' && X  >= 'a' )?TRUE:FALSE)
#define IS_ALPHA(X)           ((IS_UPPER_ALPHA(X) || IS_LOWER_ALPHA(X)|| IS_UNDERSCORE(X) )?TRUE:FALSE)
#define IS_NUM(X)             (strchr("0123456789",X)?TRUE:FALSE)
#define IS_ALPHANUM(X)        ((IS_ALPHA(X) || IS_NUM(X) )?TRUE:FALSE)

/* some utility functions */
static char* appendchar(char *str, char ch)
{
  if(str==NULL)
    std::cerr << "ConfigFile: Could not b appened into a NULL string.\n";
  int length = strlen(str);
  if(length == BUF_SIZE -1 ){
    std::cerr << "ConfigFile:appendchar could not append more.Buffer full.\n";
    exit(0);
  }
  str[length]=ch;
  str[length+1]=0; // append the null-termination character. //
}

/*  printsessions() */
static void print_sessions()
{
  if(head== NULL) 
  {
    printf("There are no sessions to printed\n");
    return;
  }
  
  session * current_session ;
  current_session= head;
  while( current_session != NULL)
  {
    printf("Session :%s\n" , current_session->session_name);
    current_session = current_session->next; 
  }  
}

/* add new session */
static int add_session(const char* session_name )
{
  // if session alraedy exists //
  if( config_file_session_into(session_name) )
    return FALSE;

  // if the current session is NULL //
  // then allocate some memory for that //
  session* current_session ;
  if( head == NULL)
  {
    head =(session*) malloc( sizeof( session));
    head->next =NULL;
    current_session = head;
  }else{
    current_session = head;
    while( current_session->next != NULL)
    {
      current_session = current_session->next;  
    }    
    session * new_session = (session*) malloc(sizeof(session));
    current_session->next = new_session;
    current_session = new_session;
    new_session->next = NULL;
  }
  
  /*
  session* current_session = head;
  
  while( current_session->next != NULL)
  {
    current_session = current_session->next; 
  }
  // :TODO: some fix is needed here // 
  if( current_session != head )
  {
    current_session->next = (session*) malloc( sizeof(session));
    current_session = current_session->next;
  }
  */
  current_session->session_name = strdup( session_name);
  current_session->next=NULL;
  current_session->head_key_value = NULL;
  ::current_session = current_session;
  /* default return value */
  return 0;
}

/* put new keyword value couple into session */
static int put_into_session(session *current_session, \
const char *keyword , const char * value )
{
   key_value * current_key_value = current_session->head_key_value;
   if(current_key_value !=  NULL){
     while(current_key_value->next != NULL)
     {
      current_key_value = current_key_value->next; 
     }
   
     current_key_value->next =(key_value*) malloc( sizeof(key_value));
      current_key_value = current_key_value->next;
   }
   else{
    current_session->head_key_value = (key_value*) malloc( sizeof(key_value));
    current_key_value = current_session->head_key_value;     
   }
   
   /* allocate memory to hold string keyword 
      and copy it here.
    */
   int size = strlen(keyword)+1;
   current_key_value->key = (char*) malloc(size);
   strcpy(current_key_value->key, keyword);
   
   /* allocate memory to hold string value 
    * and copy it here.
    */
   size = strlen(value)+1;
   current_key_value->value = (char*) malloc(size);
   strcpy(current_key_value->value, value);
   current_key_value->next = NULL;
}

/* forward declaration */
static int free_key_value(key_value* /* ptr_key_value */);

/* free memory allocated by the session data
 * structure*/
static int free_sessions(session * ptr_session){
  // if there are next sessions in the link list 
  // then
  if(ptr_session->next != NULL)
  {
   free_sessions(ptr_session->next);
  }
  
  /* fre the current session */
  free( ptr_session->session_name);
  free_key_value(ptr_session->head_key_value);
  free(ptr_session);
}

/*
 * free memory allocated by the key_value pair
 * data structure 
 */
static int free_key_value ( key_value * ptr_key_value)
{
  if( ptr_key_value->next != NULL )
  {
    /* recursive function would be called */
    free_key_value( ptr_key_value->next);
  }
  
  /* free the current allocated memory in 
   * current structure 
   */
  free( ptr_key_value->key);
  free( ptr_key_value->value);
  free (ptr_key_value);
}

static char input_buffer[ BUF_SIZE ];

int config_file_open(const char* file_name){
  /* check for the prerequisites */
  if(!file_name)
    {
      std::cerr << "ConfigFile: file_name should not be NULL \n";
      exit(0);
    }
  fd=fopen(file_name , "r");
  if (fd==NULL)
    { 
      char * error_msg = (char*)malloc(BUF_SIZE);
      sprintf( error_msg,"ConfigFile: could not open the file :%s\n",file_name);
      std::cerr << error_msg ;
      free(error_msg);
      exit(0);      
    } 
  /* default return */
  head = NULL;
  current_session = NULL;
  return 0;
}

/* de-initialize and free memories associated with.
 * 
 */
int config_file_dinit()
{
  if(head != NULL)
  {
    // clean up the memory //
    free_sessions(head);
  }
  head = NULL;
  current_session =NULL;
  fd = NULL;
  
  /* default return value */
  return 0;  
}

/* parse the configuration file.  */
int config_file_parse()

{
  is_parsed = TRUE ;
  if( fd == NULL)
    {
      std::cerr << "ConfigFile: please call config_file_open() before \
                   \n            calling config_file_parse(). "<< std::endl;
      exit(0);		   
    }
    int count ;
    int state = CONFIG_STATE_START;
    int last_state = CONFIG_STATE_START; /* :TODO: update the current state 
                                          *         in the next increment
                                          *        */
    char * current_string = (char*) malloc(BUF_SIZE);
    char * current_value = (char*) malloc(BUF_SIZE);
    char * current_key =(char*) malloc(BUF_SIZE);
    int is_eof= FALSE;
    while(true){
      count = fread(input_buffer,1,BUF_SIZE,fd );
      if( count == 0)
		is_eof=TRUE;	
	
      int i =0;
      while (count > i|| is_eof){
	  char current_char = input_buffer[i];
      if( count == 0 )
			  current_char = '\n';
	  i++;
	  // printf("%c" ,current_char);
		  
	  switch (state )
	  {
	    case CONFIG_STATE_START:
	      if(current_char == '[')
	      {
	        state = CONFIG_STATE_1;
	        break;
	      }
	      // ignore the white spaces //
	      if( IS_WHITE_SPACE(current_char) )  
	      {
	        state=CONFIG_STATE_START;
	        break;
	      }
	      // ignore new lines as well //
	      if( IS_NEW_LINE(current_char))
	      {
	        break;
	      }
	    
	      if( IS_ALPHANUM(current_char))
	      {
	        state=CONFIG_STATE_3;
	        i--;
	        break;
	      }
	      if( current_char == 0 )
	      {
	        break; 
	      }
	    
	      else{
	        std::cerr << "ConfigFile: parsing error have occurrend\
	                   \nLast known state is CONFIG_STATE_START.\n" ;
	        exit(0);
	      }
	    	    
	      break;
	    case CONFIG_STATE_1:
	      // ignore white spaces //
	      if ( IS_WHITE_SPACE(current_char) )
	      {
	        break; 
	      }
	    
	      if( IS_ALPHANUM(current_char)) 
	      {
	         state = CONFIG_STATE_1;
	         appendchar(current_string,current_char); 
	         state= CONFIG_STATE_1; // state would not be changed.//
	         break;
	      }
	    
	      if( current_char == '\\' )
	      {
	        state = CONFIG_STATE_2;
	        break;
	      }
	      if( current_char == ']')
	      {
	        add_session(current_string); 
	        current_string[0] = 0; // again null terminate //
	        state =  CONFIG_STATE_START ;
	        break;
	      }
	      else{
	        std::cerr <<"ConfigFile: Expected alphanum or / or ]. \n";
	        exit(0);
	      }
	      // ::NEVER REACH::
	      break;
	    case CONFIG_STATE_2:
	      if( IS_WHITE_SPACE(current_char)) 
	      {
	         std::cerr <<"ConfigFile: Not Expected a whitespace after \'\\\'.\n"<<std::endl;
	         exit(0);
	      }
	      if( current_char == 't')
	      {
	        appendchar(current_string,'\t');
	        state = CONFIG_STATE_1;
	        break;
	      }
	      if ( current_char == 's' )
	      {
	        appendchar(current_string,' ');
	        state = CONFIG_STATE_1;
	        break;
	      }
	    
	      if( current_char == 'n')
	      {
	        appendchar(current_string, '\n');
	        state=CONFIG_STATE_1;
	        break;
	      }
	      else{
	        std::cerr <<"ConfigFile: Expected \\t \\n or \\t.\n" ;
	        exit(0);
	      }
	      // ::NEVER REACH::    
	      break;
	    case CONFIG_STATE_3:
	      if(IS_ALPHANUM(current_char) )
	      {
	        appendchar(current_string,current_char);
	        break;
	      }
	      if(current_char == '\\')
	      {
	        state = CONFIG_STATE_4;
	        break;
	      }
	      if( current_char == '=')
	      {
	        strcpy(current_key,current_string);
	        current_string[0] =0; // bring null termination back to zero position. //
	        state = CONFIG_STATE_5;
	        break;
	      }
	      else{
	        std::cerr << "ConfigFile: expected / = or alphanum.\n";
	        exit(0);
	      }
	      // ::NEVER REACH::
	      break;
	    case CONFIG_STATE_4:
	      if( IS_WHITE_SPACE(current_char)) 
	      {
	         std::cerr <<"ConfigFile: Not Expected a whitespace after \'\\\'.\n"<<std::endl;
	         exit(0);
	      }
	      if( current_char == 't')
	      {
	        appendchar(current_string,'\t');
	        state = CONFIG_STATE_3;
	        break;
	      }
	      if ( current_char == 's' )
	      {
	        appendchar(current_string,' ');
	        state = CONFIG_STATE_3;
	        break;
	      }
	    
	      if( current_char == 'n')
	      {
	        appendchar(current_string, '\n');
	        state=CONFIG_STATE_3;
	        break;
	      }
	      else{
	        std::cerr <<"ConfigFile: Expected \\t \\n or \\t.\n" ;
	        exit(0);
	      }
	    
	      // :NEVER_REACH:
	      break;
	    case CONFIG_STATE_5:
	      /* ignore white spaces */
	      if( IS_WHITE_SPACE(current_char))
	        break;
	    
	      if(IS_ALPHANUM(current_char) )
	      {
	        appendchar(current_string,current_char);
	        state = CONFIG_STATE_5; // keep in the current state //
	        break;
	      }
	    
	      if( current_char == '\\')
	      {
	        state = CONFIG_STATE_6;
	        break;
	      }
	    
	      if( (IS_NEW_LINE(current_char) )|| is_eof== TRUE )
	      {
           // printf("current string is: %s\n",current_string);
			state = CONFIG_STATE_START;
	        strcpy(current_value,current_string); 
	        if(current_session ==NULL)
	        {
		      std::cerr << "ConfigFile: You need be inside a session before you have key=value pairs.\n";
		      exit(0);
	        }
	      
	        put_into_session(current_session,current_key,current_value);
	        current_string[0]= 0; // bring the null termination back to zero position //
	        break;
	      }
	    
	      else{
	        std::cerr << "ConfigFile: expected a newline ,alphanum or '\\' operator.\n";
	        exit(0);
	      }
	      // ::NEVER REACHES:: //
	      break;
	    case CONFIG_STATE_6:
	      if( IS_WHITE_SPACE(current_char)) 
	      {
	         std::cerr <<"ConfigFile: Not Expected a whitespace after \'\\\'.\n"<<std::endl;
	         exit(0);
	      }
	      if( current_char == 't')
	      {
	        appendchar(current_string,'\t');
	        state = CONFIG_STATE_5;
	        break;
	      }
	      if ( current_char == 's' )
	      {
	        appendchar(current_string,' ');
	        state = CONFIG_STATE_5;
	        break;
	      }
	    
	      if( current_char == 'n')
	      {
	        appendchar(current_string, '\n');
	        state=CONFIG_STATE_5;
	        break;
	      }
	      else{
	        std::cerr <<"ConfigFile: Expected \\t \\n or \\t.\n" ;
	        exit(0);
	      }
	      // :NEVER REACHES: //
	      break;	    
	    default:
	      std::cerr <<"ConfigFile: Parsing error occurred ! exiting...\n";
	      exit(0);
	  }
	  if( is_eof )
		  break;
	 
    }
    if( is_eof )
	{ 
	  break;	 
	}
  }
  // we no longer need these memory //
  free(current_string);
  free(current_value);
  free(current_key);
  fclose(fd);
  is_parsed = TRUE ;
}

int config_file_add_new_session(const char* session_name)
{
  add_session(session_name);
}

int config_file_add_entry(const char* key, const char* value)
{
  if ( key == NULL|| value == NULL)
  {
    printf("config_file_add_entry: 'key' or 'value' parameters could not be NULL\n");
    exit(0);
  }
  
  
  if(current_session == NULL)
  {
    printf("config_file: You should have to enter into a session before you call config_file_add_entry() \n");
    return 0; 
  }
  /* find whether the entry already exists */
    key_value* current_key_value = current_session->head_key_value;
    while(TRUE){
      if( current_key_value == NULL){
	break;
      }
      
      if( strcmp( current_key_value->key,key) == 0)
      {
	//strdup( value);
	free( current_key_value->value);
	current_key_value->value = strdup(value);
	return 1;
      }
      current_key_value= current_key_value->next;
    }   
  put_into_session(current_session,key,value); 
  return 1;
}

static void encode_with_escape_character(const char* input, char* output,int size)
{
  const char * current_pos= input ;
  char * buffer = (char*) malloc(BUF_SIZE);
  buffer[0] = 0;
  
  while(TRUE)
  {
    if( *current_pos == 0 )
    {
     appendchar(buffer,0);
     break;
    }
    switch( *current_pos) {
      //:TODO: process space tab newline and other special characters that we does support //
      case 32: // space character //
         appendchar(buffer,'\\');
	 appendchar(buffer,'s');
	 break;
	 
      case 9: // tab character //
         appendchar(buffer, '\\');
	 appendchar(buffer,'t');
	 break;
     case 10: // new line second //
       appendchar(buffer,'\\');
       appendchar(buffer,'n');
       break; 
      case 13: // newline character //
	appendchar(buffer,'\\');
	appendchar(buffer,'n');
	break;
      
      default:
        appendchar(buffer,*current_pos);
	break;
      
    }
    current_pos++;
    
  }
  int length = strlen(buffer);
  if(length > size)
  {
    printf("config_file:encode_with_escape_character() function buffer overflow.Please have \n\
            more buffer space.\n");
    exit(0);
  }
  strcpy( output,buffer );
  free(buffer);
  return;  
}

int config_file_write_to_file(const char* file_name){
 if( file_name == NULL)
 {
   printf("config_file: config_file_write() filename parameter could not be NULL\n");
   exit(0);
 }
 FILE* fd = fopen(file_name,"w");
 if(fd == NULL)
 {
   printf("config_file: config_file_write_to_file() , file: %s could not be open for\n\
           writing\n");
   exit(0);
 }
 
 char * buffer = (char*) malloc(BUF_SIZE);
 session * current_session = head ;
 while(TRUE){
   
   if(current_session == NULL)
     break;
   putc('[',fd);
   encode_with_escape_character(current_session->session_name,buffer,BUF_SIZE);
   fprintf(fd,"%s",buffer);
   putc(']',fd);
   putc('\r',fd);
   putc('\n',fd);
   key_value* current_key_value = current_session->head_key_value ;
   while(TRUE){
    if(current_key_value == NULL)
      break;
    encode_with_escape_character(current_key_value->key,buffer,BUF_SIZE);
    fprintf(fd,"%s",buffer);
    putc('=',fd);
    
    encode_with_escape_character(current_key_value->value,buffer,BUF_SIZE);
    fprintf(fd,"%s",buffer);
    putc('\r',fd);
    putc('\n',fd);
      
    current_key_value= current_key_value->next; 
   } 
   putc('\r',fd);
   putc('\n',fd);
   current_session = current_session->next;
 }
 // in success // 
 free(buffer);
 fclose(fd);
 //printf("config_file:configuration settings have been successfully written into the file\n");
 return TRUE; 
}



